/*
    This file is part of the Foothill College Web GIS.

    Foothill College Web GIS is free software: you can redistribute it 
    and/or modify it under the terms of the GNU General Public License 
    as published by the Free Software Foundation, either version 3 of 
    the License, or (at your option) any later version.

    Foothill College Web GIS is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with Foothill College Web GIS. If not, see 
    <http://www.gnu.org/licenses/>.

*/

// It's an object; this is the only thing that should go in the global namespace
// All variables should be either var variablename = ... or this.varname = ...
// NOTHING should be variablename = ... (this is a global variable)
// Also, jQuery must be referenced as jQuery, not $, in case it's ever used
// on a site where they use another Javascript framework that also uses $
// Call it like new fhWebMap( id of element where the map should be drawn )
// fhWebMap.init()
function fhWebMap (mapElemId, options) {
    // We'll need this later
    this.mapElemId = mapElemId;

    // Allow people to not pass in an options array and accept the defaults
    if (options == undefined) options = {};

    this.options = options;

    // Parse the options hash
    // namespace: the DOM ID namespace. Only needs to be changed when there are
    // multiple maps on a page.
    this.namespace = options.namespace?options.namespace:'fhWebMap';
    this.options.layers = options.layers?options.layers:['buildings', 'rooms'];
    this.options.zoomLevel = options.zoomLevel?options.zoomLevel:'detect';

    this.log = function (msg) {
	// use the JS console if available
  // use window.console because if you try to pass in plain console, in many browsers it will crash saying the variable is undefined
	if (this._undefined(window.console) == false) {
	    console.log(msg);
	}
	else {
	    // log to the log area div, which is defined if console is undefined
      jQuery('#' + this.namespace + '_logArea').append(msg + '<br/>');
	}
    }// this.log

    // draw the initial map
    this.init = function () {
	// Let's see if I can explain why this is necessary - it's kind of 
        // black magic. We use a closure (in the form of an anonymous function)
        // in the many statements below, and within the scope of those 
        // functions, `this` refers to the them. But local variables are 
	// included in a closure's namespace, so setting 
	// instance allows us to access class
        // methods of fhWebMap as instance.<whatever>
	var instance = this;

	// Add the HTML features to the containing div
	// All element ids should start with fhWebMap, to keep things clean
	var html = '';
	html += '<div id="' + this.namespace + '_toolbar" class="fhWebMap_toolbar"></div>';
	html += '<div id="' + this.namespace + '_tools"></div>';
	html += '<div id="' + this.namespace + '_map" style="width: 100%; height: 100%"></div>';
   
  // Build a div to put log messages in if console.log is undefined. This can be hidden with CSS when not doing development
  if (this._undefined(window.console)) {
      html += '<div id="' + this.namespace + '_logArea" class="fhWebMap_logArea">';
  }
  
	// We put a clearing div at the bottom because elements are often
	// floated over the map (e.g. layer selector), and we don't want them
	// to mess up other parts of the page.
	html += '<div style="clear:both"></div>';


	// Put it in, overwriting anything else
	var ourDiv = jQuery('#' + this.mapElemId);
	ourDiv.html(html);
	
	// And add our class to the div
	// Don't overwrite other classes
	ourDiv.attr('class', ourDiv.attr('class') + ' fhWebMap');

	// Calculate the initial zoom level based on screen size so that
	// the entire campus is shown, unless it's been overridden
	if (this.options.zoomLevel == 'detect') {
	    // the map will fill the div, even though there are some other
	    // elements drawn over it
	    var w = ourDiv.width();
	    var h = ourDiv.height();

	    if     (w > 1800 && h > 650) var zoomLevel = 18;
	    else if (w > 750 && h > 525) var zoomLevel = 17;
	    else if (w > 379 && h > 260) var zoomLevel = 16;
	    else                         var zoomLevel = 15;
	}
	else {
	    // It's been overridden
	    var zoomLevel = this.options.zoomLevel;
	}

	var foothillLatLng = new google.maps.LatLng(37.36126, -122.12871);
	var mapOpts = {
	    zoom: zoomLevel,
	    center: foothillLatLng,
	    mapTypeId: google.maps.MapTypeId.SATELLITE,
      tilt: 0 // 45 degree imagery has numerous problems - see issues 3 and 10. Our map doesn't overlay correctly,
      // and 1300 and 4400 are shown even though they've been demolished.
	};
	
	this.map = new google.maps.Map(
	    // Didn't use jQuery('#' + mapElemId) because it returns a
	    // jQuery object, not a DOM object, and I'm not sure if that
	    // will play nice with Google Maps
	    document.getElementById(this.namespace + '_map'),
	    mapOpts
	);

	this.layers = {};

	var kmlLayerOptions = {
	    preserveViewport: true, // Shouldn't change viewport when we toggle
	    suppressInfoWindows: true // Clicking on a room shouldn't give
	    // a popup with meaningless data
	};

	// We append this random number to each SVN url to force the
	// KML files to be fetched new each time
	var random = Math.random();

	// Add the Parking Lots layer
	// this one is the easiest; no multiple levels!
	this.layers.parking = new google.maps.KmlLayer('http://foothill-web-gis.googlecode.com/svn/tags/data-0.1/parking.kml', kmlLayerOptions);
	this.log('added parking lots');

	// Add the building envelopes layer
	// again, no levels
	this.layers.buildings = new google.maps.KmlLayer('http://foothill-web-gis.googlecode.com/svn/tags/data-0.1/buildings.kml', kmlLayerOptions);
	this.log('added building footprints');

	// Eventually, add multiple levels
	this.layers.rooms = new google.maps.KmlLayer(
	    'http://foothill-web-gis.googlecode.com/svn/tags/data-0.1/rooms.kml', 
	    kmlLayerOptions
	);
	this.log('added rooms');

	// Add all the layers they requested
	// note it is this.options.layers, not just this.layers!
	// They mean different things!
  // We don't use this.options.layers.forEach anymore because IE doesn't support it.
  // We set this variable and then refer to it so that we don't have to check the length of the list on each iteration (doesn't matter much here, but in a long
  // list it could)
  var listLen = this.options.layers.length;
  for (i = 0; i < listLen; i++) {
	    // in a closure, so use instance
	    instance.showLayer(this.options.layers[i]);
	}

	var ns = this.namespace; // to condense the code below

	/* This is a little convenience function we use in the HTML code
	   below. It returns 'checked="true"' if the layer name is shown,
	   '' if it isn't. The reason we have to do this is that some browsers
	   (ok, most browsers) draw the checkbox as checked if the value of
	   checked is undefined, *even if* it is false. */
	function checked(layerName) {
	    // Use instance instead of this because this refers to this func
	    if (instance._shown(layerName)) return 'checked="true"';
	    else return '';
	}

	// This is the actual selection dialog box
	// the reason we put in value=... is so that we know what
	// layer was checked/unchecked in the event handler

	var control = jQuery('\
<div id="' + ns + '_layerSelector" class="fhWebMap_widget fhWebMap_layerSelector">\
<!-- Wrap all this in a span so that we can get the width of a non block\
     element. No need for ID; we can refer to as #ns_LayerSelector span -->\
<span>\
<a href="#" id="' + ns + '_layerSelectorButton">Layers</a><br/>\
<!-- Add style="display: none" if you want the layer browser \
     closed by default -->\
<div id="' + ns + '_layerSelectorSlideBox" \
   class="fhWebMap_layerSelector fhWebMap_slideBox">\
<input type="checkbox" ' + checked('parking') + '" \
  id="' + ns + '_layerSelectorParking" \
  class="' + ns + '_layerSelectorBox" value="parking" />\
Parking Lots<br/>\
<input type="checkbox" ' + checked('buildings') + '" \
  id="' + ns + '_layerSelectorBuildings" \
  class="' + ns + '_layerSelectorBox" value="buildings" />\
Buildings<br/>\
<input type="checkbox" ' + checked('rooms') + '" \
  id="' + ns + '_layerSelectorRooms" \
  class="' + ns + '_layerSelectorBox" value="rooms" />\
Rooms</div></span></div>');

	// bind the events

	// first, the hide/show layer handler event
	// we do this with the control variable, not after we add it to the map,
	// because it is added asynchronously and who knows how long it might
	// take for that to happen, so we can't rely on the elements being
	// created when we assign handlers, if we do it that way.
	control.find('#' + ns + '_layerSelectorButton').click(function (e) {
	    if (jQuery('#' + ns + '_layerSelectorSlideBox').css('display') == 'none') {
		// Not shown; show it
		instance.log('showing layer browser');
		jQuery('#' + ns + '_layerSelectorSlideBox').slideDown(500);
	    }
	    else {
		// hide it
		instance.log('hiding layer browser');
		jQuery('#' + ns + '_layerSelectorSlideBox').slideUp(500);
	    }

	    // Set the width and height from the actual width and height
	    // Otherwise it blocks dragging the map b/c it intercepts
	    // drag events b/c the control has a higher z-index (see issue #2)

	    // We get the current size of the span and set the div to be that
	    // size. The span is not a block element, so it *should* scale with
	    // the content.
	    // This code doesn't seem to be necessary in all browsers
	    // (but won't hurt); just having the <span> is enough for e.g.
	    // Firefox and Chrome. I suspect it's needed in IE.
	    // We set a timeout so that the function doesn't run until the 
	    // box is fully retracted.
	    setTimeout( function () {
		var theControl = jQuery('#' + ns + '_layerSelector');
		var theControlSpan = jQuery('#' + ns + '_layerSelector span');
		theControl.height(theControlSpan.height() + 'px');
		theControl.width(theControlSpan.width() + 'px');
	    }, 550);
	});

	// now, the layer events
	// bind to the class so we just have one event handler that manages any
	// select/deselect
	control.find('.' + ns + '_layerSelectorBox').change(function (e) {
	    // The checkbox that triggered this even
	    var box = e.target;
	    // don't use `this` - note above
	    instance.log('toggling layer ' + box.value);
	    if (box.checked) {
		// toggle the layer on, it's just been checked
		// do special stuff for rooms, they're actually 3 layers
		// (or they will be)
		if (box.value == 'rooms') {
		    instance.showLayer('rooms');
		}
		else {
		    // toggle the layer
		    instance.showLayer(box.value);
		}
	    }
	    else {
		// turn off the layer
		// do special stuff for rooms, they're actually 3 layers
		// (or they will be)
		if (box.value == 'rooms') {
		    instance.hideLayer('rooms');
		}
		else {
		    // toggle the layer
		    instance.hideLayer(box.value);
		}
	    }
	});

	// Add the control to the map
	this.map.controls[google.maps.ControlPosition.TOP_RIGHT].push(control[0]);
	
    } // this.init

    // Returns true if a layer is currently shown on a map
    // the _ is taken from the Python convention of naming private functions
    // with names starting with _; JavaScript has no way to create private 
    // functions
    this._shown = function (layerName) {
	if (this.layers[layerName].map == null) {
	    // not shown
	    return false;
	}
	else {
	    return true;
	}
    } // this.shown
    
    //Check if an object is undefined in a browser-neutral way
    // if you say x == undefined, some browsers will crash saying that you can't do a comparison to x b/c it is undefined
      this._undefined = function (obj) {
        if (typeof obj == 'undefined') {
          return true;
        }
        else {
          return false;
        }
      }
  
    this.hideLayer = function (layerName) {
	this.layers[layerName].setMap(null);
	this.log('hiding layer "' + layerName + '"');
    }
    
    this.showLayer = function (layerName) {
	this.layers[layerName].setMap(this.map);
	this.log('showing layer "' + layerName + '"');
	
	// Make sure rooms are on top if we're messing with the building
	// footprint layer
	if (layerName == 'buildings' && this._shown('rooms')) {
	    this.log('bringing "rooms" layer to foreground');
	    this.hideLayer('rooms');
	    this.showLayer('rooms'); // Last layer drawn is on top
	}
    }
}